using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Web;
using System.Xml;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using MyWebPagesStarterKit.Sitemaps.Utilities;

namespace MyWebPagesStarterKit.Sitemaps
{
    public class SitemapWriter : IDisposable
    {
        private readonly string _siteHostUrl;
        private readonly XmlWriter _writer;

        public event LastModifiedResolveEventHandler LastModifiedResolve;

        public SitemapWriter(XmlWriter writer, string siteHostUrl)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }
            ParameterUtils.ValidateStringNotNullOrEmpty(siteHostUrl, "siteHostUrl");
            if (siteHostUrl.IndexOf(':') == -1)
            {
                throw new ArgumentException("Invalid host url");
            }
            this._writer = writer;
            this._siteHostUrl = siteHostUrl;
        }

        public void Close()
        {
            this._writer.Close();
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.Close();
            }
        }

        public void Flush()
        {
            this._writer.Flush();
        }

        private DateTime? ResolveLastModifiedDate(SiteMapNode siteMapNode)
        {
            LastModifiedResolveEventHandler lastModifiedResolve = this.LastModifiedResolve;
            if (lastModifiedResolve != null)
            {
                foreach (LastModifiedResolveEventHandler handler2 in lastModifiedResolve.GetInvocationList())
                {
                    DateTime? nullable = handler2(this, new LastModifiedResolveEventArgs(siteMapNode));
                    if (nullable.HasValue)
                    {
                        return nullable;
                    }
                }
            }
            return null;
        }

        void IDisposable.Dispose()
        {
            this.Dispose(true);
        }

        private void WriteDouble(XmlWriter writer, string propertyName, SiteMapNode siteMapNode)
        {
            string str = siteMapNode[propertyName];
            if (!string.IsNullOrEmpty(str))
            {
                double num;
                try
                {
                    num = Convert.ToDouble(str);
                }
                catch (Exception exception)
                {
                    throw new SitemapWriterException(string.Format("Error writing {0}: " + exception.Message, propertyName), siteMapNode.Key, exception);
                }
                if ((num < 0.0) || (num > 1.0))
                {
                    throw new SitemapWriterException(string.Format("{0} must be between 0.0 and 1.0", propertyName), siteMapNode.Key);
                }
                writer.WriteElementString(propertyName, Math.Round(num, 1, MidpointRounding.AwayFromZero).ToString("0.0", CultureInfo.InvariantCulture));
            }
        }

        public void WriteEndRootElement()
        {
            this._writer.WriteEndElement();
            this._writer.WriteEndDocument();
        }

        public void WriteEndUrlElement()
        {
            this._writer.WriteEndElement();
        }

        private void WriteEnum<T>(XmlWriter writer, string propertyName, SiteMapNode siteMapNode) where T: struct
        {
            string str = siteMapNode[propertyName];
            if (!string.IsNullOrEmpty(str))
            {
                T local;
                try
                {
                    local = EnumUtils.Parse<T>(str);
                }
                catch (Exception exception)
                {
                    throw new SitemapWriterException(string.Format("Error writing {0}: " + exception.Message, propertyName), siteMapNode.Key, exception);
                }
                writer.WriteElementString(propertyName, local.ToString().ToLowerInvariant());
            }
        }

        private void WriteLastModifiedDate(XmlWriter writer, SiteMapNode siteMapNode)
        {
            DateTime? nullable = this.ResolveLastModifiedDate(siteMapNode);
            if (!nullable.HasValue)
            {
                string strA = siteMapNode["lastmod"];
                if (string.Compare(strA, "now", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    nullable = new DateTime?(DateTime.Now);
                }
                else if (!string.IsNullOrEmpty(strA))
                {
                    try
                    {
                        nullable = new DateTime?(Convert.ToDateTime(strA));
                    }
                    catch (Exception exception)
                    {
                        throw new SitemapWriterException(string.Format("Error writing {0}: " + exception.Message, "lastmod"), siteMapNode.Key, exception);
                    }
                } 
            }
            if (nullable.HasValue)
            {
                writer.WriteElementString("lastmod", nullable.Value.ToString("yyyy-MM-ddTHH:mm:sszzzzzz", CultureInfo.InvariantCulture));
            }
        }

      


        /// <summary>
        /// Rewrites all sections that has been configured in web.config
        /// </summary>
        public void WriteRewriteSections()
        {
            //Remeber to remove section that is removed from mwpsk in web.config rewrite/sections
            NameValueCollection config = (NameValueCollection)ConfigurationManager.GetSection("rewrite/sections"); 
            
            if (config == null)
                return;
            
            string[] keys = config.AllKeys;

            foreach (string key in keys)
            {
                string val = config[key];
                
                List<ISection> sections = SectionLoader.GetInstance().LoadSections(val);

                if (sections != null)
                {
                    foreach (ISection section in sections)
                    {
                        if (section.GetType().Name.Equals(val))
                        {
                            if (section.Entries != null)
                            {
                                foreach (DataRow row in section.Entries.Rows)
                                {
                                    this.WriteStartUrlElement();
                                    this._writer.WriteElementString("loc", this._siteHostUrl + key + row["RewriteUrl"].ToString());
                                    SiteMapNode siteMapNode = SiteMap.Provider.FindSiteMapNodeFromKey(section.PageID);
                                    if (siteMapNode != null)
                                        this.WriteLastModifiedDate(this._writer, siteMapNode);
                                    else //Ensure all datime are writter
                                        this._writer.WriteElementString("lastmod", DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzzzzz", CultureInfo.InvariantCulture));
                                    this.WriteEndUrlElement(); 
                                }
                            }
                        }
                    }
                }
            }
        }

      

        public void WriteSiteMap(SiteMapNode baseNode)
        {
            this.WriteStartRootElement();
            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            this._writer.WriteComment(string.Format("Generator by Enterprisesoft, {0}", " 2.3 "));
            this.WriteUrlElements(SiteMap.RootNode);
            this.WriteRewriteSections();
            this.WriteEndRootElement();
        }

        public void WriteStartRootElement()
        {
            this._writer.WriteStartDocument();
            this._writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
        }

        public void WriteStartUrlElement()
        {
            this._writer.WriteStartElement("url");
        }

        public void WriteUrlElement(SiteMapNode siteMapNode)
        {
            string url = siteMapNode.Url;
            if (url.EndsWith(".aspx") || url.EndsWith("/"))
                this.WriteUrlElement(siteMapNode, false);
        }

        private void WriteUrlElement(SiteMapNode siteMapNode, bool recurseWriteChildren)
        {
            string str = siteMapNode["sitemapsIgnore"];
            if (string.IsNullOrEmpty(str) || !Convert.ToBoolean(str))
            {

                string url = siteMapNode.Url; //Changes to remove duplicate root 2009-08-31
                if (url.EndsWith(".aspx") || url.EndsWith("/"))
                {
                    this.WriteStartUrlElement();
                    this.WriteUrlValueElements(siteMapNode);
                    this.WriteEndUrlElement();
                }
                if (recurseWriteChildren && siteMapNode.HasChildNodes)
                {
                    for (int i = 0; i < siteMapNode.ChildNodes.Count; i++)
                    {
                        this.WriteUrlElement(siteMapNode.ChildNodes[i], recurseWriteChildren);
                    }
                }
                
            }
        }

        public void WriteUrlElements(SiteMapNode siteMapNode)
        {
            this.WriteUrlElement(siteMapNode, true);
        }

        public void WriteUrlValueElements(SiteMapNode siteMapNode)
        {
            if (siteMapNode == null)
            {
                throw new ArgumentNullException("siteMapNode");
            }
            string url = siteMapNode.Url;
            if (UrlPath.IsRelativeUrl(url))
            {
                throw new SitemapWriterException("Url cannot be relative", siteMapNode.Key);
            }
            if (UrlPath.IsValidVirtualPathWithoutProtocol(url))
            {
                url = this._siteHostUrl + siteMapNode.Url;
            }

            //Changes to remove duplicate root 2009-08-31
            if (url.EndsWith(".aspx") || url.EndsWith("/"))
            {
                this._writer.WriteElementString("loc", url);
                this.WriteDouble(this._writer, "priority", siteMapNode);
                this.WriteLastModifiedDate(this._writer, siteMapNode);
                this.WriteEnum<ChangeFrequency>(this._writer, "changefreq", siteMapNode);
            }
        }
    }
}
